using System;
using System.Globalization;
using System.Collections.Generic;
using System.Security.Authentication;
using System.Text;
using System.Web;
using System.IO;
using System.Xml;
using System.Net;


namespace MSN_gui
{
    public sealed class SecurityToken
    {
        public const string UserAgent = "User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; .NET CLR 2.0.50727; IDCRL 4.100.313.1; IDCRL-cfg 4.0.5633.0; App msnmsgr.exe, 8.1.178.0, {7108E71A-9926-4FCB-BCC9-9A9D3F32E423})";

        static readonly string EnvTpl = "<?xml version=\"1.0\" encoding=\"UTF-8\"?><Envelope xmlns=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:wsse=\"http://schemas.xmlsoap.org/ws/2003/06/secext\" xmlns:saml=\"urn:oasis:names:tc:SAML:1.0:assertion\" xmlns:wsp=\"http://schemas.xmlsoap.org/ws/2002/12/policy\" xmlns:wsu=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd\" xmlns:wsa=\"http://schemas.xmlsoap.org/ws/2004/03/addressing\" xmlns:wssc=\"http://schemas.xmlsoap.org/ws/2004/04/sc\" xmlns:wst=\"http://schemas.xmlsoap.org/ws/2004/04/trust\">"
            + "<Header><ps:AuthInfo xmlns:ps=\"http://schemas.microsoft.com/Passport/SoapServices/PPCRL\" Id=\"PPAuthInfo\">"
            + "<ps:HostingApp>{3:B}</ps:HostingApp><ps:BinaryVersion>4</ps:BinaryVersion><ps:UIVersion>1</ps:UIVersion>"
            + "<ps:Cookies></ps:Cookies><ps:RequestParams>AQAAAAIAAABsYwQAAAAzMDg0</ps:RequestParams></ps:AuthInfo>"
            + "<wsse:Security><wsse:UsernameToken Id=\"user\"><wsse:Username>{0}</wsse:Username><wsse:Password>{1}</wsse:Password></wsse:UsernameToken></wsse:Security></Header>"
            + "<Body><ps:RequestMultipleSecurityTokens xmlns:ps=\"http://schemas.microsoft.com/Passport/SoapServices/PPCRL\" Id=\"RSTS\">"
            + "<wst:RequestSecurityToken Id=\"RST0\"><wst:RequestType>http://schemas.xmlsoap.org/ws/2004/04/security/trust/Issue</wst:RequestType>"
            + "<wsp:AppliesTo><wsa:EndpointReference><wsa:Address>http://Passport.NET/tb</wsa:Address></wsa:EndpointReference></wsp:AppliesTo>"
            + "</wst:RequestSecurityToken><wst:RequestSecurityToken Id=\"RST1\"><wst:RequestType>http://schemas.xmlsoap.org/ws/2004/04/security/trust/Issue</wst:RequestType>"
            + "<wsp:AppliesTo><wsa:EndpointReference><wsa:Address>messenger.msn.com</wsa:Address></wsa:EndpointReference></wsp:AppliesTo>"
            + "<wsse:PolicyReference URI=\"?{2}\"></wsse:PolicyReference></wst:RequestSecurityToken></ps:RequestMultipleSecurityTokens></Body></Envelope>";
        readonly string un;
        readonly string pw;
        readonly string ch;

        string t;
        string p;
        DateTime x;
        long id;
        /// <summary>
        /// "t" part of the token.
        /// </summary>
        public string Ticket
        {
            get
            {
                if (DateTime.Now >= x)
                    Renew();
                return t;
            }
        }
        /// <summary>
        /// "p" part of the token.
        /// </summary>
        public string Passport
        {
            get
            {
                if (DateTime.Now >= x)
                    Renew();
                return p;
            }
        }
        /// <summary>
        /// Token's expiry date.
        /// </summary>
        public DateTime Expiry
        {
            get
            {
                return x;
            }
        }
        /// <summary>
        /// Owner's Passport Unique ID.
        /// </summary>
        public long PUID
        {
            get
            {
                return id;
            }
        }

        /// <summary>
        /// Tries to retrieve a security token for the specified user.
        /// </summary>
        /// <param name="username">User's e-mail address.</param>
        /// <param name="password">User's password.</param>
        public SecurityToken(string username, string password) : this(username, password, null) { }
        /// <summary>
        /// Tries to retrieve a security token for the specified user, with a specified Tweener (TWN) challenge string.
        /// </summary>
        /// <param name="username">User's e-mail address.</param>
        /// <param name="password">User's password.</param>
        /// <param name="challenge">TWN challenge string.</param>
        public SecurityToken(string username, string password, string challenge)
        {
            un = username;
            pw = password;
            ch = challenge;
            Renew();
        }
        /// <summary>
        /// Initializes an instance of the SecurityToken class with some token information.
        /// </summary>
        /// <param name="binaryToken">A string representing the token.</param>
        /// <param name="puid">Owner's Passport Unique ID.</param>
        /// <param name="expiry">Token's expiry date.</param>
        public SecurityToken(string binaryToken, long puid, DateTime expiry)
        {
            un = null;
            pw = null;
            ch = null;
            SetToken(binaryToken, puid, expiry);
        }

        static string MakeEnvelope(string username, string password, string challenge)
        {
            if (username == null)
                username = "";
            if (password == null)
                password = "";
            if (challenge == null)
                challenge = "";
            return string.Format(EnvTpl, XmlEncode(username), XmlEncode(password), XmlEncode(Uri.UnescapeDataString(challenge).Replace(',', '&')), new Guid("7108E71A-9926-4FCB-BCC9-9A9D3F32E423"));
        }
        static string XmlEncode(string data)
        {
            return HttpUtility.HtmlEncode(data);
        }
        static string XmlDecode(string data)
        {
            return HttpUtility.HtmlDecode(data);
        }
        /// <summary>
        /// Forces the security token to be renewed, even if it hasn't expired.
        /// </summary>
        /// <remarks>
        /// This method is also used internally to renew the token if it has expired.
        /// </remarks>
        public void Renew()
        {
            if (string.IsNullOrEmpty(un) || string.IsNullOrEmpty(pw))
                throw new InvalidOperationException();
            HttpWebRequest req;
            HttpWebResponse rsp;
            req = (HttpWebRequest)WebRequest.Create("https://loginnet.passport.com/RST.srf");
            req.Accept = "text/*";
            req.UserAgent = UserAgent;
            req.KeepAlive = true;
            string env = MakeEnvelope(un, pw, ch);
            /***/
            StreamWriter sw = new StreamWriter("C:\\Env.xml");
            sw.Write(env);
            sw.Close();
            /****/
            byte[] dat = Encoding.UTF8.GetBytes(env);
            req.ContentLength = dat.LongLength;
            req.Method = "POST";
            req.ProtocolVersion = HttpVersion.Version11;
            Stream s = req.GetRequestStream();
            s.Write(dat, 0, dat.Length);
            s.Close();
            try { rsp = (HttpWebResponse)req.GetResponse(); }
            catch { throw new AuthenticationException(); }
            s = rsp.GetResponseStream();
            XmlDocument xml = new XmlDocument();
            s = rsp.GetResponseStream();
            xml.Load(s);
            s.Close();
            rsp.Close();
            XmlNode xn = xml.GetElementsByTagName("psf:PUID")[0];
            long puid = long.Parse(xn.InnerText, NumberStyles.HexNumber);
            xn = xml.GetElementsByTagName("wsu:Expires")[0];
            DateTime expiry = ParseTimeStamp(xn.InnerText);
            xn = xml.GetElementsByTagName("wsse:BinarySecurityToken")[0];
            SetToken(xn.InnerText, puid, expiry);
        }

        static DateTime ParseTimeStamp(string ts)
        {
            int T = ts.IndexOf('T');
            int Z = ts.IndexOf('Z');
            string date = ts.Substring(0, T);
            string time = ts.Substring(T + 1, Z - T - 1);
            string[] d = date.Split('-');
            string[] t = time.Split(':', '.');
            int[] dd = { int.Parse(d[0]), int.Parse(d[1]), int.Parse(d[2]) };
            int[] tt = { int.Parse(t[0]), int.Parse(t[1]), int.Parse(t[2]) };
            if (t.Length >= 4)
                tt = new int[] { tt[0], tt[1], tt[2], int.Parse(t[3]) };
            if (tt.Length >= 4)
                return new DateTime(dd[0], dd[1], dd[2], tt[0], tt[1], tt[2], tt[3]);
            return new DateTime(dd[0], dd[1], dd[2], tt[0], tt[1], tt[2]);
        }

        void SetToken(string binaryToken, long puid, DateTime expiry)
        {
            string[] tokenParts = binaryToken.Split('&');
            t = null;
            p = null;
            x = expiry;
            id = puid;
            foreach (string part in tokenParts)
            {
                string key = part.Substring(0, part.IndexOf('='));
                string value = part.Substring(part.IndexOf('=') + 1);
                switch (key.ToLower())
                {
                    case "t":
                        t = value;
                        break;
                    case "p":
                        p = value;
                        break;
                }
            }
        }

        /// <summary>
        /// Gets the binary token.
        /// </summary>
        /// <returns>A string representing the token. It will look like "t=...&p=...".</returns>
        public override string ToString()
        {
            return "t=" + t + "&p=" + p;
        }
    }
}
